import XLSX from 'xlsx';

export default class ExportExcel {
  // 默认的表格样式
  defaultCellStyle = {
    alignment: {
      horizontal: 'center',
      vertical: 'center',
    },
  }
  exportData (tableData, tableHeader, fileName, mergesList, colsW) {
    if (!Array.isArray(tableData) || tableData.length < 1) {
      throw { type: 'error', message: '请选择需要导出的数据！' }
    }
    let sheetName = fileName
    // excel表头
    let excelHeader = this.buildHeader(tableHeader)
    // 头部行数，用来固定表头
    let headerRows = excelHeader.length
    // 提取数据
    let dataList = this.extractData(tableData, tableHeader)
    excelHeader.push(...dataList, [])
    // 计算合并
    let merges = this.doMerges(excelHeader)
    merges = merges.concat(mergesList)
    // 生成sheet
    let ws = this.aoa_to_sheet(excelHeader, headerRows)
    // 单元格合并
    ws['!merges'] = merges
    // 头部冻结
    ws['!freeze'] = {
      xSplit: '1',
      ySplit: '' + headerRows,
      topLeftCell: 'B' + (headerRows + 1),
      activePane: 'bottomRight',
      state: 'frozen',
    }
    // 列宽
    ws['!cols'] = colsW
    let workbook = {
      SheetNames: [sheetName],
      Sheets: {},
    }

    workbook.Sheets[sheetName] = ws
    // excel样式
    let wopts = {
      bookType: 'xlsx',
      bookSST: false,
      type: 'binary',
      cellStyles: true,
    }
    // 表格样式
    let wbout = XLSX.write(workbook, wopts, { defaultCellStyle: this.defaultCellStyle })
    let blob = new Blob([this.s2ab(wbout)], { type: 'application/octet-stream' })
    this.openDownloadXLSXDialog(blob, sheetName + '.xlsx')
    // window.saveAs(blob, sheetName + '.xlsx')
  }

  /**
   * 构建excel表头
   * @param revealList 列表页面展示的表头
   * @returns {[]} excel表格展示的表头
   */
  buildHeader (revealList) {
    let excelHeader = []
    // 构建生成excel表头需要的数据结构
    this.getHeader(revealList, excelHeader, 0, 0)
    // 多行表头长短不一，短的向长的看齐，不够的补上行合并占位符
    let max = Math.max(...excelHeader.map((a) => a.length))
    excelHeader.filter((e) => e.length < max).forEach((e) => this.pushRowSpanPlaceHolder(e, max - e.length))
    return excelHeader
  }

  /**
   * 生成头部
   * @param headers 展示的头部
   * @param excelHeader excel头部
   * @param deep 深度
   * @param perOffset 前置偏移量
   * @returns {number}  后置偏移量
   */
  getHeader (headers, excelHeader, deep, perOffset) {
    let offset = 0
    let cur = excelHeader[deep]
    if (!cur) {
      cur = excelHeader[deep] = []
    }
    // 填充行合并占位符
    this.pushRowSpanPlaceHolder(cur, perOffset - cur.length)
    for (let i = 0; i < headers.length; i++) {
      let head = headers[i]
      cur.push(head.name)
      if (head.hasOwnProperty('child') && Array.isArray(head.child) && head.child.length > 0) {
        let childOffset = this.getHeader(head.child, excelHeader, deep + 1, cur.length - 1)
        // 填充列合并占位符
        this.pushColSpanPlaceHolder(cur, childOffset - 1)
        offset += childOffset
      } else {
        offset++
      }
    }
    return offset
  }

  /**
   * 根据选中的数据和展示的列，生成结果
   * @param selectionData
   * @param revealList
   */
  extractData (selectionData, revealList) {
    // 列
    let headerList = this.flat(revealList)
    // 导出的结果集
    let excelRows = []
    // 如果有child集合的话会用到
    let dataKeys = new Set(Object.keys(selectionData[0]))
    selectionData.some((e) => {
      if (e.child && e.child.length > 0) {
        let childKeys = Object.keys(e.child[0])
        for (let i = 0; i < childKeys.length; i++) {
          dataKeys.delete(childKeys[i])
        }
        return true
      }
    })
    this.flatData(selectionData, (list) => {
      excelRows.push(...this.buildExcelRow(dataKeys, headerList, list))
    })
    return excelRows
  }

  buildExcelRow (mainKeys, headers, rawDataList) {
    // 合计行
    let sumCols = []
    // 数据行
    let rows = []
    for (let i = 0; i < rawDataList.length; i++) {
      let cols = []
      let rawData = rawDataList[i]
      // 提取数据
      for (let j = 0; j < headers.length; j++) {
        let header = headers[j]
        // 父元素键需要行合并
        if (rawData['rowSpan'] === 0 && mainKeys.has(header.prop)) {
          cols.push('!$ROW_SPAN_PLACEHOLDER')
        } else {
          let value
          if (typeof header.exeFun === 'function') {
            value = header.exeFun(rawData)
          } else {
            value = rawData[header.prop]
          }
          cols.push(value)
          // 如果该列需要合计,并且是数字类型
          if (header['summable'] && typeof value === 'number') {
            sumCols[j] = (sumCols[j] ? sumCols[j] : 0) + value
          }
        }
      }
      rows.push(cols)
    }
    // 如果有合计行
    if (sumCols.length > 0) {
      rows.push(...this.sumRowHandle(sumCols))
    }
    return rows
  }
  sumRowHandle (sumCols) {
    //TODO
    return []
  }

  /**
   * @description: 需要合并的数据
   * @param {*} arr
   * @return {*}
   */
  doMerges (arr) {
    // 要么横向合并 要么纵向合并
    let deep = arr.length
    let merges = []
    for (let y = 0; y < deep; y++) {
      // 先处理横向合并
      let row = arr[y]
      let colSpan = 0
      for (let x = 0; x < row.length; x++) {
        if (row[x] === '!$COL_SPAN_PLACEHOLDER') {
          row[x] = undefined
          if (x + 1 === row.length) {
            merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x } })
          }
          colSpan++
        } else if (colSpan > 0 && x > colSpan) {
          merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 } })
          colSpan = 0
        } else {
          colSpan = 0
        }
      }
    }
    // 再处理纵向合并
    let colLength = arr[0].length
    for (let x = 0; x < colLength; x++) {
      let rowSpan = 0
      for (let y = 0; y < deep; y++) {
        if (arr[y][x] === '!$ROW_SPAN_PLACEHOLDER') {
          arr[y][x] = undefined
          if (y + 1 === deep) {
            merges.push({ s: { r: y - rowSpan, c: x }, e: { r: y, c: x } })
          }
          rowSpan++
        } else if (rowSpan > 0 && y > rowSpan) {
          merges.push({ s: { r: y - rowSpan - 1, c: x }, e: { r: y - 1, c: x } })
          rowSpan = 0
        } else {
          rowSpan = 0
        }
      }
    }
    return merges
  }

  /**
   * 从github复制过来的
   */
  aoa_to_sheet (data, headerRows) {
    const ws = {}
    const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } }
    for (let R = 0; R !== data.length; ++R) {
      for (let C = 0; C !== data[R].length; ++C) {
        if (range.s.r > R) {
          range.s.r = R
        }
        if (range.s.c > C) {
          range.s.c = C
        }
        if (range.e.r < R) {
          range.e.r = R
        }
        if (range.e.c < C) {
          range.e.c = C
        }
        /// 这里生成cell的时候，使用上面定义的默认样式
        const cell = {
          v: data[R][C] || '',
          s: Object.assign({}, this.defaultCellStyle), //这里是因为防止浅复制导致数据地址指向同一数据
        }
        // 头部列表加边框
        if (R < headerRows) {
          const style = Object.assign(cell.s, {
            border: {
              top: { style: 'thin', color: { rgb: '000000' } },
              left: { style: 'thin', color: { rgb: '000000' } },
              bottom: { style: 'thin', color: { rgb: '000000' } },
              right: { style: 'thin', color: { rgb: '000000' } },
            },
            fill: {
              patternType: 'solid',
              fgColor: { theme: 3, tint: 0.3999755851924192, rgb: 'F5F7FA' },
              bgColor: { theme: 7, tint: 0.3999755851924192, rgb: 'F5F7FA' },
            },
          })
          cell.s = style
        }
        const cell_ref = XLSX.utils.encode_cell({ c: C, r: R })
        if (typeof cell.v === 'number') {
          cell.t = 'n'
        } else if (typeof cell.v === 'boolean') {
          cell.t = 'b'
        } else {
          cell.t = 's'
        }
        ws[cell_ref] = cell
      }
    }
    if (range.s.c < 10000000) {
      ws['!ref'] = XLSX.utils.encode_range(range)
    }
    return ws
  }
  /**
   * 填充行合并占位符
   * */
  pushRowSpanPlaceHolder (arr, count) {
    for (let i = 0; i < count; i++) {
      arr.push('!$ROW_SPAN_PLACEHOLDER')
    }
  }
  // 填充列合并占位符
  pushColSpanPlaceHolder (arr, count) {
    for (let i = 0; i < count; i++) {
      arr.push('!$COL_SPAN_PLACEHOLDER')
    }
  }
  /**
   * @description:铺平数组
   * @param {*} list 表格数据
   * @param {*} eachDataCallBack 构建的行数据
   * @return {*}
   */
  flatData (list, eachDataCallBack) {
    let resultList = []
    for (let i = 0; i < list.length; i++) {
      let data = list[i]
      let rawDataList = []
      // 每个子元素都和父元素合并成一条数据
      if (data.child && data.child.length > 0) {
        for (let j = 0; j < data.child.length; j++) {
          delete data.child[j].bsm
          let copy = Object.assign({}, data, data.child[j])
          rawDataList.push(copy)
          copy['rowSpan'] = j > 0 ? 0 : data.child.length
        }
      } else {
        data['rowSpan'] = 1
        rawDataList.push(data)
      }
      resultList.push(...rawDataList)
      if (typeof eachDataCallBack === 'function') {
        eachDataCallBack(rawDataList)
      }
    }
    return resultList
  }
  // 扁平头部
  flat (revealList) {
    let result = []
    revealList.forEach((e) => {
      if (e.hasOwnProperty('child')) {
        result.push(...this.flat(e.child))
      } else if (e.hasOwnProperty('exeFun')) {
        result.push(e)
      } else if (e.hasOwnProperty('prop')) {
        result.push(e)
      }
    })
    return result
  }
  s2ab (s) {
    let buf = new ArrayBuffer(s.length)
    let view = new Uint8Array(buf)
    for (let i = 0; i !== s.length; ++i) {
      view[i] = s.charCodeAt(i) & 0xff
    }
    return buf
  }
  openDownloadXLSXDialog (url, saveName) {
    if (typeof url == 'object' && url instanceof Blob) {
      url = URL.createObjectURL(url) // 创建blob地址
    }
    var aLink = document.createElement('a')
    aLink.href = url
    aLink.download = saveName || '' // HTML5新增的属性，指定保存文件名，可以不要后缀，注意，file:///模式下不会生效
    var event
    if (window.MouseEvent) {
      event = new MouseEvent('click')
    } else {
      event = document.createEvent('MouseEvents')
      event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
    }
    aLink.dispatchEvent(event)
  }
  // 文件流下载excel
 static downloadExcel (res, name) {
    if (!res || !name) {
      return false
    }
    let blob = new Blob([res], {
      type: "application/vnd.ms-excel"
    });
    let fileName = `${name}.xlsx`;
    if (window.navigator.msSaveOrOpenBlob) {
      navigator.msSaveBlob(blob, fileName);
    } else {
      var link = document.createElement("a");
      link.href = window.URL.createObjectURL(blob);
      link.download = fileName;
      link.click();
      //释放内存
      window.URL.revokeObjectURL(link.href);
    }
  }
}
